# app/utils/device_manager.py
import asyncio
import hashlib
import os

import psutil
import subprocess
import time
import json
import logging
from datetime import datetime as dt, datetime

from sqlalchemy.orm import Session
from starlette.responses import JSONResponse
from starlette.websockets import WebSocketDisconnect, WebSocket

from app.core.config import settings
from app.core.database import get_db_context, update_device_status, get_device
from app.models.models import Device, DeviceDeletionLog
from app.utils.device_cache import device_cache
from app.utils.update_queue import update_queue
from app.utils.ws_manager import frontend_manager, device_info_manager, ws_manager
from fastapi.responses import PlainTextResponse


logger = logging.getLogger(__name__)

heartbeat_queue = asyncio.Queue()

# 在模块级别创建进程对象
_current_process = psutil.Process(os.getpid())


async def handle_device_activation(message: dict, db: Session):
    """处理设备激活"""
    dev_id = message.get("dev_id")
    if not dev_id:
        return {"error": "Missing dev_id"}

    # 更新设备状态
    update_data = {
        "online_status": "online",
        "last_active": time.time()
    }
    if await update_device_status(dev_id, update_data, db):
        return {
            "response": "activate",
            "result": "success",
            "dev_id": dev_id
        }
    else:
        return {
            "response": "activate",
            "result": "error",
            "error": "Device not found"
        }


async def process_heartbeat_batch():
    """处理心跳批处理"""
    batch_size = 50  # 每批处理50个心跳
    batch_delay = 1  # 最大等待时间1秒

    while True:
        batch = []
        start_time = time.time()

        # 收集一批心跳
        while len(batch) < batch_size and (time.time() - start_time) < batch_delay:
            try:
                # 非阻塞获取
                item = await asyncio.wait_for(heartbeat_queue.get(), timeout=0.1)
                batch.append(item)
            except asyncio.TimeoutError:
                # 超时继续
                pass
            except asyncio.QueueEmpty:
                # 队列为空
                break

        if batch:
            try:
                # 使用单个数据库会话处理整个批次
                async with get_db_context() as db:
                    for dev_id, update_data in batch:
                        device = db.query(Device).filter(Device.dev_id == dev_id).first()
                        if device:
                            for key, value in update_data.items():
                                setattr(device, key, value)
                    db.commit()

                    # 更新缓存
                    for dev_id, update_data in batch:
                        try:
                            await device_cache.update_device(dev_id, update_data)
                        except Exception as e:
                            logger.error(f"更新缓存失败: {e}")

                    logger.info(f"成功处理 {len(batch)} 个心跳")
            except Exception as e:
                logger.error(f"心跳批处理失败: {e}")
        else:
            # 没有数据时休眠
            await asyncio.sleep(0.1)


async def handle_device_heartbeat(message: dict):
    """处理设备心跳 - 完全基于缓存"""
    dev_id = message.get("dev_id")
    if not dev_id:
        logger.warning("心跳消息缺少 dev_id")
        return

    # 只更新变化的字段
    # 准备更新数据
    update_data = {
        "dev_id": dev_id,  # 确保包含 dev_id
        "last_active": time.time(),
        "online_status": "online"
    }

    # 合并其他可能变化的字段
    dev_info = message.get("dev_info", {})
    for key in ["cpu_usage", "mem_usage", "brightness", "volume"]:
        if key in dev_info:
            update_data[key] = dev_info[key]

    # 更新缓存
    try:
        await device_cache.update_device(dev_id, update_data)
        logger.debug(f"设备 {dev_id} 心跳缓存更新成功")
    except Exception as e:
        logger.error(f"更新设备缓存失败: {e}")

    # 添加到批量更新队列（异步更新数据库）
    await update_queue.add_update(dev_id, update_data)

    # # 通知前端
    # await frontend_manager.broadcast({
    #     "type": "device_heartbeat",
    #     "dev_id": dev_id,
    #     "data": update_data
    # })
    # await device_info_manager.broadcast_device_info(dev_id, update_data)


async def mark_device_offline(dev_id: str, device: dict):
    """统一标记设备为离线状态"""
    activated_time = time.time()
    cst_time = dt.fromtimestamp(activated_time).strftime("%Y-%m-%d %H:%M:%S")

    # 创建完整的离线状态更新数据
    offline_update = {
        "online_status": "offline",
        "cpu_usage": 0,
        "mem_usage": 0,
        "ip_address": "",
        "brightness": None,
        "volume": None,
        "last_active": activated_time
    }

    # 更新缓存
    updated_device = {**device, **offline_update}
    await device_cache.update_device(dev_id, updated_device)

    # 添加到批量更新队列
    await update_queue.add_update(dev_id, offline_update)

    return updated_device


async def handle_device_disconnect(dev_id: str):
    """处理设备断开连接 - 立即更新状态"""
    try:
        # 从缓存获取设备当前状态
        device = await device_cache.get_device(dev_id)
        if not device:
            logger.warning(f"设备 {dev_id} 在缓存中不存在")
            return

        # 使用统一的离线标记函数
        await mark_device_offline(dev_id, device)
        logger.info(f"设备 {dev_id} 断开连接，已立即标记为离线")
    except Exception as e:
        logger.error(f"处理设备断开连接失败: {e}")


async def check_device_online_status():
    """检查设备在线状态 - 基于缓存"""
    # 获取当前时间
    activated_time = time.time()
    cst_time = dt.fromtimestamp(activated_time).strftime("%Y-%m-%d %H:%M:%S")
    updated_devices = []
    # 获取当前系统资源使用情况
    cpu_percent = get_cpu_usage()

    mem_info = _current_process.memory_info()
    mem_usage_mb = mem_info.rss / (1024 * 1024)

    logger.info(">>> 开始检查设备在线状态 <<<")
    logger.info(f"当前时间: {cst_time}")
    logger.info(
        f"程序资源使用: CPU={cpu_percent:.1f}%, 内存={mem_usage_mb:.2f}MB")

    try:
        # 从缓存获取所有设备
        devices = await device_cache.get_all_devices()
        if not devices:
            logger.warning("没有获取到设备列表，跳过检查")
            return updated_devices

        online_devices = [device for device in devices if device.get("online_status") == "online"]
        logger.info(f"获取到 {len(devices)} 台设备")

        for device in devices:
            # 确保设备字典包含 'dev_id' 键
            if "dev_id" not in device:
                logger.warning(f"设备字典缺少 'dev_id' 键: {device}")
                continue

            dev_id = device["dev_id"]
            last_active = device.get("last_active", 0)
            logger.debug(
                f"设备 {dev_id} 最后活跃时间: {last_active}, 差值: {activated_time - last_active}")

            # 如果最后活跃时间超过超时时间，则认为离线
            if activated_time - last_active > settings.DEVICE_HEARTBEAT_TIMEOUT:
                if device.get("online_status") == "online":
                    # logger.info(
                    #     f"设备 {dev_id} 超过{settings.DEVICE_HEARTBEAT_TIMEOUT}秒无活动，标记为离线")

                    # 使用统一的离线标记函数
                    await mark_device_offline(dev_id, device)
                    updated_devices.append(dev_id)
    except Exception as e:
        logger.error(f"检查设备在线状态失败: {e}")

    if online_devices is not None:
        logger.info(f"在线设备 {len(online_devices) - len(updated_devices)} 台")

    return updated_devices


def get_cpu_usage():
    # 获取当前CPU时间
    current_cpu_time = _current_process.cpu_times().user + _current_process.cpu_times().system

    # 获取当前时间
    activated_time = time.time()

    # 计算与上次的差值
    if hasattr(get_cpu_usage, "last_cpu_time") and hasattr(get_cpu_usage, "last_time"):
        time_diff = activated_time - get_cpu_usage.last_time
        cpu_diff = current_cpu_time - get_cpu_usage.last_cpu_time
        cpu_percent = (cpu_diff / time_diff) * 100
    else:
        cpu_percent = 0.0

    # 保存当前值供下次使用
    get_cpu_usage.last_cpu_time = current_cpu_time
    get_cpu_usage.last_time = activated_time

    return cpu_percent


# app/utils/device_manager.py
from app.models.models import DeviceRegistrationLog
from fastapi import Request
import json


async def factory_activate_device(device_data: dict, request: Request = None):
    """设备出厂激活 - 直接操作数据库，注册后刷新缓存"""
    # 添加详细的请求日志
    logger.info(
        f"收到设备激活请求: IP={request.client.host if request and request.client else 'N/A'}")
    logger.info(f"请求数据: {json.dumps(device_data, indent=2)}")

    # 准备日志数据
    log_data = {
        "registration_time": datetime.now(),
        "success": False,
        "request_data": device_data,
        "response_data": None,
        "error_message": None,
        "dev_id": None,
        "ip_address": None,
        "user_agent": None
    }

    # 添加请求信息
    if request:
        log_data["ip_address"] = request.client.host if request.client else None
        log_data["user_agent"] = request.headers.get("user-agent")

    try:
        # 获取设备ID
        dev_id = device_data.get("dev_id", "")
        if not dev_id:
            error_msg = "缺少dev_id"
            logger.error(f"设备激活失败: {error_msg}, 请求数据: {json.dumps(device_data)}")
            # log_data["error_message"] = error_msg
            return JSONResponse(content={}, status_code=403)

        # 获取设备序列信息
        cpu_ser = device_data.get("cpu_ser", "")
        if not cpu_ser:
            error_msg = "缺少cpu_ser"
            logger.error(f"设备激活失败: {error_msg}, 请求数据: {json.dumps(device_data)}")
            # log_data["error_message"] = error_msg
            return JSONResponse(content={}, status_code=403)

        # 验证设备ID是否由CPU序列号加盐后取MD5值生成
        expected_cpu_serial_with_salt = f"{cpu_ser}&fangchao#"
        expected_dev_id = hashlib.md5(expected_cpu_serial_with_salt.encode()).hexdigest()

        if expected_dev_id != dev_id:
            error_msg = f"设备ID验证失败: 预期={expected_dev_id}, 实际={dev_id}"
            logger.error(f"设备激活失败: {error_msg}, 请求数据: {json.dumps(device_data)}")
            # log_data["error_message"] = error_msg
            return JSONResponse(content={}, status_code=403)

        # 记录验证通过
        logger.info(f"设备ID验证通过: dev_id={dev_id}, cpu_ser={cpu_ser}")

        async with get_db_context() as db:
            # 直接查询数据库检查设备是否已存在
            device = db.query(Device).filter(Device.dev_id == dev_id).first()

            if device:
                device_name = device_data.get("dev_name", device.dev_name)
                # 更新设备信息 - 直接操作数据库
                logger.info(f"设备 {dev_id} 已存在，更新设备信息")

                # 准备更新数据
                update_data = {
                    "emmc_ser": device_data.get("emmc_ser", device.emmc_ser),
                    "mac_add": device_data.get("mac_add", device.mac_add),
                    "city": device_data.get("city", device.city),
                    "firmware_version": device_data.get("firmware_version",
                                                        device.firmware_version),
                    "app_version": device_data.get("app_version", device.app_version),
                }

                # 更新数据库
                for key, value in update_data.items():
                    setattr(device, key, value)
                db.commit()

                # 刷新设备缓存 - 从数据库重新加载数据
                db.refresh(device)
                device_dict = device.to_dict()
                await device_cache.update_device(dev_id, device_dict)
                logger.info(f"设备 {dev_id} 缓存已刷新")


                log_data["success"] = True
                log_data["dev_id"] = dev_id
                log_data["response_data"] = device_name

                logger.info(f"设备更新成功: {dev_id}, 名称: {device_name}")
                return PlainTextResponse(device_name)
            else:
                # 创建设备记录 - 直接操作数据库
                logger.info(f"设备 {dev_id} 不存在，创建新设备")

                # 获取当前批次号
                current_batch = get_current_batch_number()
                # 获取下一个设备编号
                next_number = await get_next_device_number(db)
                device_name = f"Holospeed_{current_batch}_{next_number:05d}"
                # activated_time 只记录第一次注册时间
                activation_time = dt.now().strftime("%Y-%m-%d %H:%M:%S")

                new_device = Device(
                    dev_id=dev_id,
                    dev_name=device_name,
                    cpu_ser=cpu_ser,
                    emmc_ser=device_data.get("emmc_ser", ""),
                    mac_add=device_data.get("mac_add", ""),
                    activated_time=activation_time,
                    cpu_usage=0,
                    mem_usage=0,
                    city=device_data.get("city", ""),
                    firmware_version=device_data.get("firmware_version", ""),
                    app_version=device_data.get("app_version", ""),
                    online_status="offline",
                    brightness=0,
                    volume=0,
                    last_active=time.time()
                )

                try:
                    db.add(new_device)
                    db.commit()
                    logger.info(f"设备 {dev_id} 创建成功")

                    # 刷新设备缓存 - 从数据库重新加载数据
                    db.refresh(new_device)
                    device_dict = new_device.to_dict()
                    await device_cache.update_device(dev_id, device_dict)
                    logger.info(f"设备 {dev_id} 缓存已刷新")
                except Exception as e:
                    db.rollback()
                    logger.error(f"设备创建失败: {str(e)}")
                    return JSONResponse(content={"error": "设备创建失败"}, status_code=500)

                log_data["success"] = True
                log_data["dev_id"] = dev_id
                log_data["response_data"] = device_name

                logger.info(f"设备注册成功: {dev_id}, 名称: {device_name}")
                return PlainTextResponse(device_name)

    except Exception as e:
        logger.error(f"设备注册失败: {str(e)}")
        log_data["error_message"] = str(e)
        log_data["success"] = False
        return JSONResponse(
            content={"error": "内部服务器错误"},
            status_code=500,
        )

    finally:
        # 记录日志
        await save_registration_log(log_data)


async def handle_device_websocket(websocket: WebSocket, safe_db_operation):
    """处理设备WebSocket连接"""
    await websocket.accept()
    logger.info("WebSocket连接已接受")

    db_context = get_db_context()
    dev_id = None  # 初始化设备ID
    AUTH_TIMEOUT = 30  # 认证超时时间（秒）

    try:
        try:
            # 使用wait_for设置接收超时
            auth_data = await asyncio.wait_for(
                websocket.receive_text(),
                timeout=AUTH_TIMEOUT
            )
        except asyncio.TimeoutError:
            logger.warning(f"设备在{AUTH_TIMEOUT}秒内未发送认证信息")
            await websocket.close(code=403, reason="认证超时")
            return

        try:
            auth_message = json.loads(auth_data)
        except json.JSONDecodeError:
            logger.error("设备发送的认证消息格式错误")
            await websocket.close(code=403, reason="无效的JSON格式")
            return

        dev_id = auth_message.get("dev_id")

        if not dev_id:
            logger.warning("设备未提供dev_id")
            await websocket.close(code=403, reason="缺少设备ID")
            return

        # 验证设备ID是否存在
        async with db_context as db:
            device = await get_device(dev_id)
            if not device:
                logger.warning(f"设备 {dev_id} 不存在")
                await websocket.close(code=403, reason="设备不存在")
                return

        logger.info(f"设备 {dev_id} 连接成功")
        await ws_manager.connect_device(websocket, dev_id)

        # 检查第一条消息中是否包含开机请求
        if auth_message.get("request") == "power_on" or auth_message.get("request") == "ping":
            logger.info(f"设备 {dev_id} 在认证消息中发送了开机请求")
            # 提取设备信息
            current_config = auth_message.get("current_config", {})

            # 保存设备配置
            await save_device_config(dev_id, current_config)

            # 发送开机响应
            response = {
                "dev_id": dev_id,
                "request":"accept",
                "event_time": dt.now().strftime("%Y-%m-%d %H:%M:%S")
            }
            await websocket.send_json(response)
            logger.info(f"已发送开机响应: {response}")

        # 进入消息循环
        while True:
            data = await websocket.receive_text()
            try:
                message = json.loads(data)
                logger.info(f"收到设备消息: {message}")

                # 处理升级请求
                if message.get("request") == "power_on":
                    # 提取设备信息
                    current_config = message.get("current_config", {})
                    # 保存设备配置
                    await save_device_config(dev_id, current_config)
                    # 发送开机响应
                    response = {
                        "dev_id": dev_id,
                        "request": "accept",
                        "event_time": dt.now().strftime("%Y-%m-%d %H:%M:%S")
                    }
                    await websocket.send_json(response)
                    logger.info(f"已发送开机响应: {response}")

                # 处理远程控制响应
                elif message.get("respond") == "control_accept":
                    # 通知所有订阅该设备信息的前端控制成功
                    await device_info_manager.broadcast_device_info(dev_id, {
                        "type": "control_successful",
                        "dev_id": dev_id,
                        "event_time": dt.now().strftime("%Y-%m-%d %H:%M:%S")
                    })
                    logger.info(f"设备 {dev_id} 已接受控制请求，已通知所有订阅的前端")

                # 处理其他消息类型
                elif "dev_info" in message and "event_time" in message:
                    await handle_device_heartbeat(message)
                else:
                    logger.warning(f"未知消息类型: {message.get('type')}")
                    await websocket.send_json({
                        "error": "Unknown message type",
                        "type": message.get("type")
                    })

            except json.JSONDecodeError as e:
                logger.error(f"JSON解析错误: {e}\n接收到的数据: {data}")
                await websocket.send_json({
                    "error": "Invalid JSON format",
                    "details": str(e)
                })

    except WebSocketDisconnect:
        logger.info(f"设备 {dev_id} 断开连接")
        # 更新设备状态为离线
        await update_device_status(dev_id, {"online_status": "offline"})
        # 通知前端
        device = await get_device(dev_id)
        if device:
            await frontend_manager.broadcast({
                "type": "device_update",
                "data": device
            })
        # 断开设备连接
        await ws_manager.disconnect_device(dev_id)

    except Exception as e:
        logger.error(f"WebSocket处理错误: {str(e)}")
        if dev_id:
            await ws_manager.disconnect_device(dev_id)
        await websocket.close(code=403, reason="服务器错误")


async def save_registration_log(log_data: dict):
    """保存设备注册日志"""
    try:
        async with get_db_context() as db:
            log_entry = DeviceRegistrationLog(
                registration_time=log_data["registration_time"],
                success=log_data["success"],
                request_data=log_data["request_data"],
                response_data=log_data["response_data"],
                error_message=log_data["error_message"],
                dev_id=log_data["dev_id"],
                ip_address=log_data["ip_address"],
                user_agent=log_data["user_agent"]
            )
            db.add(log_entry)
            db.commit()
            logger.info(f"设备注册日志已保存: ID={log_entry.id}")
    except Exception as e:
        logger.error(f"保存设备注册日志失败: {str(e)}")


async def handle_upgrade_command(dev_id: str, command_data: dict):
    """处理设备升级命令"""
    logger.info(f"设备 {dev_id} 收到升级命令: {command_data}")

    # 通知前端升级开始
    await frontend_manager.broadcast({
        "type": "upgrade_started",
        "dev_id": dev_id,
        "version": command_data.get("latest_app_version")
    })

    # 模拟升级过程
    for progress in range(0, 101, 10):
        await asyncio.sleep(1)
        logger.info(f"设备 {dev_id} 升级进度: {progress}%")
        # 发送进度更新
        await frontend_manager.broadcast({
            "type": "upgrade_progress",
            "dev_id": dev_id,
            "progress": progress
        })

    logger.info(f"设备 {dev_id} 升级完成")

    # 通知前端升级完成
    await frontend_manager.broadcast({
        "type": "upgrade_completed",
        "dev_id": dev_id,
        "version": command_data.get("latest_app_version")
    })

async def save_device_config(dev_id: str, config: dict):
    """保存设备配置到数据库和缓存"""
    try:
        # 准备更新数据
        update_data = {
            "brightness": config.get("brightness"),
            "volume": config.get("volume"),
            "heartbeat_interval": config.get("heartbeat_interval"),
            "city": config.get("city")
        }

        # 更新数据库
        async with get_db_context() as db:
            device = db.query(Device).filter(Device.dev_id == dev_id).first()
            if device:
                for key, value in update_data.items():
                    setattr(device, key, value)
                db.commit()

        # 更新缓存
        current_device = await device_cache.get_device(dev_id)
        if current_device:
            updated_device = {**current_device, **update_data}
            await device_cache.update_device(dev_id, updated_device)

        logger.info(f"设备 {dev_id} 配置已保存")
    except Exception as e:
        logger.error(f"保存设备配置失败: {str(e)}")


import re
from packaging import version
import logging

logger = logging.getLogger(__name__)


async def delete_device(dev_id: str, request: Request = None):
    """删除设备 - 带日志记录"""
    # 准备日志数据
    log_data = {
        "deletion_time": datetime.now(),
        "success": False,
        "dev_id": dev_id,
        "ip_address": None,
        "user_agent": None,
        "error_message": None
    }

    # 添加请求信息
    if request:
        log_data["ip_address"] = request.client.host if request.client else None
        log_data["user_agent"] = request.headers.get("user-agent")

    try:
        if not dev_id:
            error_msg = "缺少dev_id"
            log_data["error_message"] = error_msg
            return {"error": error_msg}

        async with get_db_context() as db:
            # 检查设备是否存在
            device = db.query(Device).filter(Device.dev_id == dev_id).first()
            if not device:
                error_msg = f"设备 {dev_id} 不存在"
                log_data["error_message"] = error_msg
                return {"error": error_msg}

            # 记录设备信息用于日志 - 只使用Device模型中存在的字段
            device_info = {
                "dev_id": device.dev_id,
                "dev_name": device.dev_name,
                "cpu_ser": device.cpu_ser,
                "app_version": device.app_version,
                "firmware_version": device.firmware_version,
                "ip_address": device.ip_address,
                "city": device.city,
                "last_active": device.last_active
            }

            # 从数据库删除设备
            db.delete(device)
            db.commit()

            # 从缓存中删除设备
            try:
                await device_cache.delete_device(dev_id)
            except Exception as e:
                logger.error(f"从缓存删除设备失败: {e}")

            # 记录成功日志
            log_data["success"] = True
            log_data["device_info"] = json.dumps(device_info)

            logger.info(f"设备 {dev_id} 删除成功")
            return {"success": True, "message": f"设备 {dev_id} 已删除"}

    except Exception as e:
        logger.error(f"删除设备失败: {str(e)}")
        log_data["error_message"] = str(e)
        return {"error": f"删除设备时发生错误: {str(e)}"}

    finally:
        # 记录删除日志
        await save_deletion_log(log_data)


async def save_deletion_log(log_data: dict):
    """保存设备删除日志"""
    try:
        async with get_db_context() as db:
            log_entry = DeviceDeletionLog(
                deletion_time=log_data["deletion_time"],
                success=log_data["success"],
                dev_id=log_data["dev_id"],
                device_info=log_data.get("device_info", ""),
                ip_address=log_data["ip_address"],
                user_agent=log_data["user_agent"],
                error_message=log_data["error_message"]
            )
            db.add(log_entry)
            db.commit()
            logger.info(f"设备删除日志已保存: ID={log_entry.id}")
    except Exception as e:
        logger.error(f"保存设备删除日志失败: {str(e)}")

# 设备计数器文件
DEVICE_COUNTER_FILE = "device_counter.txt"
# 批次号文件路径
BATCH_NUMBER_FILE = "batch_number.txt" # 默认批次号


def get_current_batch_number():
    """获取当前批次号"""
    try:
        # 如果文件不存在，创建并写入默认批次号
        if not os.path.exists(BATCH_NUMBER_FILE):
            with open(BATCH_NUMBER_FILE, "w") as f:
                f.write(settings.DEFAULT_BATCH_NUMBER)
            return settings.DEFAULT_BATCH_NUMBER

        # 读取文件内容
        with open(BATCH_NUMBER_FILE, "r") as f:
            content = f.read().strip()
            return content if content else settings.DEFAULT_BATCH_NUMBER
    except Exception as e:
        logger.error(f"读取批次号文件失败: {str(e)}")
        return settings.DEFAULT_BATCH_NUMBER


async def get_next_device_number(db):
    """获取下一个设备编号"""
    # 查询所有设备名称
    devices = db.query(Device).all()

    max_number = 0
    for device in devices:
        # 解析设备名称中的数字部分
        parts = device.dev_name.split('_')
        if len(parts) >= 3:
            try:
                number = int(parts[2])
                if number > max_number:
                    max_number = number
            except ValueError:
                continue

    return max_number + 1